using System;
using System.IO;
using System.Reflection;
using System.Collections;

namespace SvnPmtHook
{
    public class TokenParser : IParse
	{
		private const string BEGIN_TOKEN_DELIMETER = "$";
		private const string END_TOKEN_DELIMETER = ":";
		private string _logContents;
		public string LogContents
		{
			get
			{
				if (String.IsNullOrEmpty(_logContents))
					throw new InvalidOperationException("LogContents was empty.  Either set the LogContents property or use the ctor that takes the log contents as a parameter.");
				return _logContents;
			}
			set
			{
				_logContents = value;
			}
		}
		public TokenParser()
			: this(String.Empty)
		{
		}
        public TokenParser(string logContents)
		{
			LogContents = logContents;
		}
		private string FindFirstTokenLine(params string[] tokens)
		{
			string result = String.Empty;
			foreach (string token in tokens)
			{
				string lineWithoutToken = this.FindLine(token);
				if (!String.IsNullOrEmpty(lineWithoutToken))
				{
					result = lineWithoutToken;
					break;
				}
			}
			return result;
		}
		private string FindLine(string token)
		{
			string result = String.Empty;
			if (String.IsNullOrEmpty(token))
			{
				return result;
			}

			using (StringReader reader = new StringReader(LogContents))
			{
				string line;
				while ((line = reader.ReadLine()) != null)
				{
					string fullToken = BEGIN_TOKEN_DELIMETER + token.Replace(BEGIN_TOKEN_DELIMETER, "").Replace(END_TOKEN_DELIMETER, "") + END_TOKEN_DELIMETER;
					if (line.StartsWith(fullToken))
					{
						result = line.Replace(fullToken, "");
						break;
					}
				}
			}
			return result;
		}

		private bool IsEnumerable<T>()
		{
			Type t = typeof(T);
			Type[] typeArgs = t.GetGenericArguments();
			return t != typeof(string)
								&& typeof(IEnumerable).IsAssignableFrom(t)
								&& t.GetGenericArguments() != null
								&& typeArgs != null
								&& typeArgs.Length == 1;
		}
		private T ParseList<T>(T @default, params string[] tokens)
		{
			Type t = typeof(T);
			Type itemType = t.GetGenericArguments()[0];
			/* The following creates a generic List of the same type as the 
			 * generic type parameter of T.
			 */
			string listTypeName = "System.Collections.Generic.List`1";
			Type generic = Type.GetType(listTypeName).MakeGenericType(itemType);

			T result = (T)Activator.CreateInstance(generic);
			MethodInfo addMethod = generic.GetMethod("Add");

			// Simply wrapping the call to the Add method on List<>.
			Action<string> add =
				(string value) =>
				{
					addMethod.Invoke(result, new object[] { Convert.ChangeType(value, itemType) });
				};

			string lineWithoutToken = this.FindFirstTokenLine(tokens);
			if (String.IsNullOrEmpty(lineWithoutToken))
				return default(T);

			string[] values = lineWithoutToken.Split(',');
			foreach (string value in values)
			{
				add(value);
			}

			if (result == null)
				return @default;

			return result;
		}
		private T ParseValue<T>(T @default, params string[] tokens)
		{
			string lineWithoutToken = this.FindFirstTokenLine(tokens);
			if (String.IsNullOrEmpty(lineWithoutToken))
				return @default;
			return (T)Convert.ChangeType(lineWithoutToken, typeof(T));
		}

		public T Parse<T>(params string[] tokens)
		{
			return Parse<T>(default(T), tokens);
		}

        public T Parse<T>(T @default, params string[] tokens)
		{
			if (IsEnumerable<T>())
			{
				return ParseList<T>(@default, tokens);
			}
			return ParseValue<T>(@default, tokens);
		}

	}
}
